import type { VarInit } from "../data/var_init";
import type { Graph } from "../graph/graph";
import { PNodeType } from "../graph/node/type";
import { shell } from "../Logger";
import { oStorage } from "../Project";
import { GetAllBundles } from "../pyBundles";
import { PyBundle } from "../pyBundles/PyBundle";
import { CodeBuilder } from "../pyCode/CodeBuilder";
import { PyVar } from "../pyCode/PyVar";
import { AstNode } from "./Ast";
import { AstCompiler } from "./AstCompiler";

export class GraphCompiler {

  /**
   * 为目标图执行检查，是否可以进行编译。
   * 并给出错误和警告
   * @param graph 目标图
   */
  static check(graph: Graph): boolean {
    // 参数检查
    const warnings: string[] = []
    const errors: string[] = []

    /**
     * 对每个节点进行检查，规则如下：
     * 
     * 对于以下情形，发出警告：
     * 1. 创建了节点但是完全不进行连接
     * 
     * 对于以下情形，发出报错：
     * 1. 节点传入了参数，但是没有连接过程
     * 2. 节点连接了过程，但是传参不全
     * 
     * 特殊情况：
     * 1. 如果结束节点不具有任何返回值，则可以
     *    不连接(空的return语句在函数结尾可以
     *    省略)
     *    
     */
    graph.nodes.forEach(node => {

      /** 关键插槽: <过程进> */
      const processIn = node.getProcessIn()

      /** 关键插槽: <过程出> */
      const processOut = node.getProcessOut()

      /** <总>插槽数量 */
      const nSlot = node.slots.length

      /** 异常状态：无插槽使用 */
      const nothingInUse = node.slots.filter(s => s.inUse).length === 0

      /** <必须进数据>插槽数量 */
      const nReqInData = node.getRequiredInDataSlots().length

      /** 异常状态：缺少参数 */
      const missingRequired = node.getRequiredInDataSlots().filter(x => x.inUse).length !== nReqInData

      /** <出数据>插槽数量 */
      const nOutData = node.getRequiredOutDataSlots().length

      /** <出数据>插槽使用数量 */
      const nOutDataInUse = node.getRequiredOutDataSlots().filter(x => x.inUse).length

      /** 异常状态：所有出数据都没使用 */
      const noOutDataInUse = nOutDataInUse === 0
      

      /**
       * 优先对过程节点进行判断，
       * 这三种节点比较负责
       */
      if (node.prototype.name === 'Start') {
        /**
         * Start必须连接<过程出>，其他数据节点随意
         */
        if (!processOut!.inUse) {
          errors.push(`节点<Start>未连接, 需要指定程序入口`)
        }
        return
      }
      else if (node.prototype.name === 'End') {
        /**
         * End允许在没有返回值的情况下不连接,
         * 有返回值就必须全连接
         */
        if (missingRequired) {
          errors.push(`节点<End>未连接, 但是函数需要传递返回值`)
        }
        return
      }
      else if (node.prototype.name === 'Condition') {
        /**
         * 传参没过程进/没传参都去下面判断，这里只
         * 判断if的特殊之处
         */
        if (!nothingInUse) {
          // 是和否至少连接一个，否则就是无意义的
          const yes = node.slots.find(x => x.prototype.name === '_out_yes')!
          const no = node.slots.find(x => x.prototype.name === '_out_no')!
          if (!yes.inUse && !no.inUse) {
            errors.push(`节点<${node.prototype.title}>至少要使用一个分支`)
            return
          }
          // ! 注意，If关于参数传入的部分并没有判断完毕，所以在这不return
        }
      } else if (node.prototype.name === 'Loop') {
        /**
         * 传参没过程进/没传参都去下面判断，这里只
         * 判断while的特殊之处
         */
        if (!nothingInUse) {
          // 必须使用子过程，否则就是空循环
          const inner = node.slots.find(x => x.prototype.name === '_out_inner')!
          if (!inner.inUse) {
            errors.push(`节点<${node.prototype.title}>缺少重复执行的内容`)
            return
          }
          // ! 注意，while关于参数传入的部分并没有判断完毕，所以在这不return
        }
      }

      // 规则：未使用的节点
      if (nothingInUse) {
        warnings.push(`节点<${node.prototype.title}>未使用`)
        return
      }

      /**
       * 到这里，所有节点都是有peer的，即使用了。
       * 接下来判断节点是否被正确使用
       * 
       * 首先看看节点有没有ProcessIn/ProcessOut
       */


      

      
      if (!processIn) {
        /**
         * 如果没有ProcessIn，有ProcessOut，那就是Start节
         * 点，在上面判断过了
         */
        if (processOut) {
          errors.push(`节点<${node.prototype.title}>: 未知错误`)
          return
        }
        /**
         * 如果ProcessIn/ProcessOut都没有，那就是Getter/Expression/PureFunc
         * 这些节点需要: 判断OutData连接没有。如果连接就是使用了，
         * 那就需要非可选的参数全连接
         */
        if (!processOut) {
          if (nOutDataInUse !== 0) {
            if (missingRequired) {
              errors.push(`节点<${node.prototype.title}>缺失参数`)
              return
            }
            else {
              // 正确使用
              return
            }
          }
        }
      }

      /**
       * 如果有ProcessIn，没有ProcessOut。第一想到的是结束节点，
       * 但是分支节点也是这样。
       * 结束节点判断过了，分支节点需要进行 参数/进过程判断
       * 
       * 如果ProcessIn/ProcessOut全都有，就是普通的函数/Setter
       * 一样需要参数/进过程判断
       * 
       * 剩下的这两类节点，判断方式都是一样的
       */
      else {
        if (missingRequired) {
          errors.push(`节点<${node.prototype.title}>缺失参数`)
          return
        }
        if (!processIn!.inUse) {
          errors.push(`节点<${node.prototype.title}>未连接过程`)
          return
        }
      }
    })

    errors.forEach(x => shell.error(x, 'GraphCompiler.check'))
    warnings.forEach(x => shell.warn(x, 'GraphCompiler.check'))

    return errors.length === 0
  }

  /**
   * compileGraph 对图进行编译，输出可执行代码
   * @param graph 编译目标图
   * @returns 
   */
  static compile(graph: Graph): string {

    if (!this.check(graph)) {
      shell.critical('编译失败', 'GraphCompiler.compile')
    }

    
    const builder = new CodeBuilder()

    // 计算使用到的模组
    const requiredModules: Set<PyBundle> = new Set()
    const allModules = GetAllBundles()
    for (const mod of allModules) {
      const modNodes = mod.exportNodes()
      // console.log('modNodesJson', modNodesJson);
      // console.log('graph.nodes', graph.nodes.map(x => JSON.stringify(x.prototype)));
      for (const node of lodash.flatten(oStorage.value!.graphs.map(x => x.nodes))) {
        if (modNodes.find(x => x.same(node.prototype))) {
          requiredModules.add(mod)
          break
        }
      }
    }
    // console.log('allModules', GetAllModules());
    // console.log('exportNodes', allModules.map(x => x.exportNodes()));
    // console.log('requiredModules', requiredModules);
    
    builder.add(PyBundle.CompileModules(Array.from(requiredModules)))

    // 计算使用到的全局变量
    const requiredVars: Set<VarInit> = new Set()
    const allData = oStorage.value!.globals
    for (const node of graph.nodes) {
      if (![PNodeType.Setter, PNodeType.Getter].includes(node.prototype.type)) {
        continue
      }
      for (const d of allData) {
        if (d.name === node.prototype.title) {
          requiredVars.add(d)
          break
        }
      }
    }

    // 全局变量
    builder.add((Array.from(requiredVars)).map(v => {
      return PyVar.fromVarInit(v).expression
    }).join('\n'))

    const 调试_强制单独编译图 = true

    if (调试_强制单独编译图) {
      const ast = AstNode.parse(graph)
      const localsCode = graph.locals.map(v => {
        return PyVar.fromVarInit(v).expression
      }).join('\n')
      const appCode = AstCompiler.compile(ast)
      
      builder.addFunction(graph.name, graph.params.map(x => x.name), `${localsCode}\n${appCode}`)
    }
    else {
      // 为了可以调用别的图，也要编译别的图
      for (const graph of oStorage.value!.graphs) {
        const ast = AstNode.parse(graph)
        const localsCode = graph.locals.map(v => {
          return PyVar.fromVarInit(v).expression
        }).join('\n')
        const appCode = AstCompiler.compile(ast)
        
        builder.addFunction(graph.name, graph.params.map(x => x.name), `${localsCode}\n${appCode}`)
      }
    }
    
    
    builder.addFunction('task', [], `return ${graph.name}()`)
    return builder.build()
  }
}